/*
 * Copyright 2011- Per Wendel
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package sardine.webserver;

import com.google.common.base.Charsets;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import sardine.*;
import sardine.HttpMethod;
import sardine.exception.ExceptionHandler;
import sardine.exception.ExceptionMapper;
import sardine.log.Logs;
import sardine.route.RouteMatched;
import sardine.route.SimpleRouteMatcher;

import java.io.IOException;
import java.util.List;
import java.util.Optional;

import static java.util.Collections.emptyList;

//import javax.servlet.Filter;
//import javax.servlet.FilterChain;
//import javax.servlet.FilterConfig;
//import javax.servlet.ServletException;
//import javax.servlet.ServletRequest;
//import javax.servlet.ServletResponse;
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;

/**
 * Filter for matching of filters and routes.
 *
 * @author Per Wendel
 */
public class MatcherFilter /*implements Filter*/ {

    private static final String ACCEPT_TYPE_REQUEST_MIME_HEADER = "Accept";

    // http重写
    private static final String HTTP_METHOD_OVERRIDE_HEADER = "X-HTTP-Method-Override";
    private static final String HTTP_METHOD_OVERRIDE_FORM = "_method";

    private SimpleRouteMatcher routeMatcher;
    private boolean isServletContext;
    private boolean hasOtherHandlers;
//
//    /**
//     * The logger.
//     */
//    private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(MatcherFilter.class);
//

    /**
     * Constructor
     *
     * @param routeMatcher     The route matcher
     * @param isServletContext If true, chain.doFilter will be invoked if request is not consumed by Spark.
     * @param hasOtherHandlers If true, do nothing if request is not consumed by Spark in order to let others handlers process the request.
     */
    public MatcherFilter(SimpleRouteMatcher routeMatcher, boolean isServletContext, boolean hasOtherHandlers) {
        this.routeMatcher = routeMatcher;
        this.isServletContext = isServletContext;
        this.hasOtherHandlers = hasOtherHandlers;
    }

    public void init(Object config/*FilterConfig filterConfig*/) {
        // config something
    }

//    public void start(FilterConfig filterConfig) {
//        //
//    }

    public void doFilter(final FullHttpRequest httpRequest, final FullHttpResponse httpResponse,
                         final ChannelHandlerContext ctx) throws IOException {

        final QueryStringDecoder query = new QueryStringDecoder(httpRequest.uri());

        String uri = query.path();

        // 如果请求头中存在 X-HTTP-Method-Override 或参数中存在 _method（拥有更高权重），
        // 且值为 GET, POST, PUT, DELETE, PATCH, OPTION, HEAD 之一，则视作相应的请求方式进行处理 这个是put等方法的实现方式
        Optional<String> method = Optional.ofNullable(httpRequest.headers().getAndConvert(HTTP_METHOD_OVERRIDE_HEADER));

        if (!method.isPresent())
            method = query.parameters().getOrDefault(HTTP_METHOD_OVERRIDE_FORM, emptyList()).stream().findFirst();

        String httpMethodStr = method.orElse(httpRequest.method().toString()).toUpperCase();


        String acceptType = httpRequest.headers().getAndConvert(ACCEPT_TYPE_REQUEST_MIME_HEADER);

        String bodyContent = null;

        RequestWrapper requestWrapper = new RequestWrapper();
        ResponseWrapper responseWrapper = new ResponseWrapper();

        Response response = RequestResponseFactory.create(httpResponse);

//        LOG.debug("httpMethod:" + httpMethodStr + ", uri: " + uri);

        try {
            // BEFORE filters
            List<RouteMatched> matchSet = routeMatcher.findTargetsForRequestedRoute(HttpMethod.BEFORE, uri, acceptType);

            for (RouteMatched filterMatch : matchSet) {
                Object filterTarget = filterMatch.target();
                if (filterTarget instanceof SimpleFilter) {
                    Request request = RequestResponseFactory.create(filterMatch, httpRequest);

                    SimpleFilter filter = (SimpleFilter) filterTarget;

                    requestWrapper.setDelegate(request);
                    responseWrapper.setDelegate(response);

                    filter.apply(requestWrapper, responseWrapper);

                    String bodyAfterFilter = Access.getBody(response);
                    if (bodyAfterFilter != null) {
                        bodyContent = bodyAfterFilter;
                    }
                }
            }
            // BEFORE filters, END

            //filter中可能重写了uri
            uri = new QueryStringDecoder(httpRequest.uri()).path(); // NOSONAR

            HttpMethod httpMethod = HttpMethod.valueOf(httpMethodStr);

            RouteMatched match = null;
            match = routeMatcher.findOneTargetForRequestedRoute(httpMethod, uri, acceptType);

            Object target = null;
            if (match != null) {
                target = match.target();
            } else if (httpMethod == HttpMethod.HEAD && bodyContent == null) {
                // See if singleton is mapped to provide default HEAD mapping
                bodyContent = routeMatcher.findOneTargetForRequestedRoute(HttpMethod.GET, uri, acceptType) != null ? "" : null;
            }

            if (target != null) {
                try {
                    String result = null;
                    if (target instanceof SimpleRoute) {
                        SimpleRoute route = ((SimpleRoute) target);

                        if (requestWrapper.getDelegate() == null) {
                            Request request = RequestResponseFactory.create(match, httpRequest);
                            requestWrapper.setDelegate(request);
                        } else {
                            requestWrapper.changeMatch(match);
                        }

                        responseWrapper.setDelegate(response);

                        Object element = route.apply(requestWrapper, responseWrapper);

                        result = route.render(element);
                        // result = element.toString(); // TODO: Remove later when render fixed
                    }
                    if (result != null) {
                        bodyContent = result;
                        response.body(bodyContent);
                    }
                } catch (HaltException hEx) { // NOSONAR
                    throw hEx; // NOSONAR
                }
            }


            // AFTER filters
            matchSet = routeMatcher.findTargetsForRequestedRoute(HttpMethod.AFTER, uri, acceptType);

            for (RouteMatched filterMatch : matchSet) {
                Object filterTarget = filterMatch.target();
                if (filterTarget instanceof SimpleFilter) {

                    if (requestWrapper.getDelegate() == null) {
                        Request request = RequestResponseFactory.create(filterMatch, httpRequest);
                        requestWrapper.setDelegate(request);
                    } else {
                        requestWrapper.changeMatch(filterMatch);
                    }

                    responseWrapper.setDelegate(response);

                    SimpleFilter filter = (SimpleFilter) filterTarget;
                    filter.apply(requestWrapper, responseWrapper);

                    String bodyAfterFilter = Access.getBody(response);

                    if (bodyAfterFilter != null) bodyContent = bodyAfterFilter;
                }
            }
            // AFTER filters, END

        } catch (HaltException hEx) {
//            LOG.debug("halt performed");
            if (httpResponse.status() == HttpResponseStatus.OK)
                httpResponse.setStatus(new HttpResponseStatus(hEx.getStatusCode(), ""));
            if (hEx.getBody() != null) {
                bodyContent = hEx.getBody();
            } else {
                bodyContent = "";
            }
        } catch (Exception e) {
            ExceptionHandler handler = ExceptionMapper.singleton().getHandler(e);
            if (handler != null) {
                handler.apply(e, requestWrapper, responseWrapper);
                String bodyAfterFilter = Access.getBody(responseWrapper.getDelegate());
                if (bodyAfterFilter != null) {
                    bodyContent = bodyAfterFilter;
                }
            } else {
//                LOG.error("", e);
                Logs.error("error in matcher filter：", e);
                httpResponse.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
                bodyContent = INTERNAL_ERROR;
            }
        }

        // If redirected and content is null set to empty string to not throw NotConsumedException
        if (bodyContent == null && responseWrapper.isRedirected()) {
            bodyContent = "";
        }

        boolean consumed = bodyContent != null;

        if (!consumed && hasOtherHandlers) {
            throw new NotConsumedException();
        }

        if (!consumed && !isServletContext) {
//            LOG.info("The requestPart route [" + uri + "] has not been mapped in Spark");
            httpResponse.setStatus(HttpResponseStatus.NOT_FOUND);
            bodyContent = String.format(NOT_FOUND);
            consumed = true;
        }

        if (consumed) {
            // Write body content
//            if (!httpResponse.isCommitted()) {

            if (!httpResponse.headers().contains(HttpHeaderNames.CONTENT_TYPE))
                httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8");


            ((DefaultFullHttpResponse) httpResponse).content().writeBytes(bodyContent.getBytes(Charsets.UTF_8));

            httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes());

            ctx.writeAndFlush(httpResponse);
//            if (httpResponse.getContentType() == null) {
//                httpResponse.setContentType("text/html; charset=utf-8");
//            }
//            httpResponse.getOutputStream().write(bodyContent.getBytes("utf-8"));

        }
//        } else if (chain != null) {
//            chain.doFilter(httpRequest, httpResponse);
//        }


//        if (consumed) {
//            // Write body content
//            if (!httpResponse.isCommitted()) {
//                if (httpResponse.getContentType() == null) {
//                    httpResponse.setContentType("text/html; charset=utf-8");
//                }
//                httpResponse.getOutputStream().write(bodyContent.getBytes("utf-8"));
//            }
//        } else if (chain != null) {
//            chain.doFilter(httpRequest, httpResponse);
//        }
    }

    public void destroy() {
        // TODO Auto-generated method stub
    }

    public static String NOT_FOUND = "<html><body><h2>404 Not found</h2></body></html>";
    private static final String INTERNAL_ERROR = "<html><body><h2>500 Internal Error</h2></body></html>";
}
